/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ThumbsDown.
 *
 * The Initial Developer of the Original Code is Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


const EXPORTED_SYMBOLS =
[
    "DirectScript",
    "RegexpScript",
    "WrappedScript",
    "CustomScript",
];

const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ScriptRunner", "resource://thumbsdown/ScriptRunner.jsm");


function BaseScript(record)
{
    this._record = record;
    this.id = record.id;
    this.name = record.name;
    this.nameLower = record.name.toLocaleLowerCase();
    this.disabled = record.disabled;
    this.type = record.type;
    this.thumbnailType = record.thumbnailType;
    this.linkType = record.linkType;
    this.linkExpr = record.linkExpr;
    this.created = (record.created ? new Date(record.created) : null);
    this.modified = (record.modified ? new Date(record.modified) : null);
    this._lastUsed = (record.lastUsed ? new Date(record.lastUsed) : null);
    this._counter = record.counter;
    this._dirty = false;
}

BaseScript.prototype =
{
    get thumbnailPattern() { if (! ("_thumbnailPattern" in this)) this._thumbnailPattern = regexp(this._record.thumbnailPattern); return this._thumbnailPattern; },
    get linkPattern()      { if (! ("_linkPattern" in this)) this._linkPattern = regexp(this._record.linkPattern); return this._linkPattern; },
    get lastUsed()    { return this._lastUsed; },
    set lastUsed(val) { this._dirty = true; this._lastUsed = val; },
    get counter()     { return this._counter; },
    set counter(val)  { this._dirty = true; this._counter = val; },
    
    match: function(thumbnail)
    {
        if (this.thumbnailType != thumbnail.type)
            return false;
        
        if (this.thumbnailPattern)
        {
            let url = thumbnail.thumbnailUrl;
            let re = this.thumbnailPattern;
            if (! (url && re && re.test(url)))
                return false;
        }
        
        if (! this._match(thumbnail, this._getLink(thumbnail)))
            return false;
        
        return true;
    },
    
    _match: function(thumbnail, link)
    {
        if (this.linkPattern)
        {
            let url = link.linkUrl;
            let re = this.linkPattern;
            if (! (url && re && re.test(url)))
                return false;
        }
        
        return true;
    },
    
    _getLink: function(thumbnail)
    {
        switch (this.linkType)
        {
            case "none":
                return null;
            case "normal":
                return thumbnail;
            case "xpath":
                return lazy({}, "linkUrl", this, this._getXpathLinkUrl, [thumbnail]);
            default:
                throw new Error("unknown linkType: " + this.linkType + " (" + this +")");
        }
    },
    
    _getXpathLinkUrl: function(thumbnail)
    {
        try
        {
            // when xpath returns attribute value, it's often relative url
            let url = this._evalXpath(thumbnail.element, this.linkExpr);
            let uri = Services.io.newURI(url, null, thumbnail.element.baseURIObject);
            return uri.spec;
        }
        catch (e)
        {
            Cu.reportError(e);
            return "";
        }
    },
    
    _evalXpath: function(context, expr)
    {
        let doc = context.ownerDocument;
        let result = doc.evaluate(expr, context, null, Ci.nsIDOMXPathResult.STRING_TYPE, null);
        let value = result.stringValue;
        return value;
    },
    
    toString: function()
    {
        return this.id + ":" + this.name;
    },
}


function DirectScript(record)
{
    BaseScript.call(this, record);
}

DirectScript.prototype =
{
    __proto__: BaseScript.prototype,
    
    eval: function(thumbnail)
    {
        let link = this._getLink(thumbnail);
        let targetUrl = link.linkUrl;
        let referer = thumbnail.doc.location.href;
        
        return [{url: targetUrl, referer: referer}];
    },
}


function RegexpScript(record)
{
    BaseScript.call(this, record);
    
    this.source = record.subType;
}

RegexpScript.prototype =
{
    __proto__: BaseScript.prototype,
    
    get pattern()  { if (! ("_pattern" in this)) this._pattern = regexp(this._record.action1); return this._pattern; },
    get template() { return this._record.action2; },
    
    _match: function(thumbnail, link)
    {
        if (! BaseScript.prototype._match.call(this, thumbnail, link))
            return false;
        
        if (! this.pattern.test(this._getSourceUrl(thumbnail, link)))
            return false;
        
        return true;
    },
    
    eval: function(thumbnail)
    {
        let link = this._getLink(thumbnail);
        let sourceUrl = this._getSourceUrl(thumbnail, link);
        let targetUrl = sourceUrl.replace(this.pattern, this.template);
        let referer = (link ? link.linkUrl : null);
        
        return [{url: targetUrl, referer: referer}];
    },
    
    _getSourceUrl: function(thumbnail, link)
    {
        switch (this.source)
        {
            case "thumbnail":
                return thumbnail.thumbnailUrl;
            case "link":
                return link.linkUrl;
            default:
                throw new Error("unknown RegExp script source: " + this.source + " (" + this + ")");
        }
    },
}


function CustomScript(record)
{
    BaseScript.call(this, record);
}

CustomScript.prototype =
{
    __proto__: BaseScript.prototype,
    
    get code() { return this._record.action1; },
    
    eval: function(thumbnail)
    {
        return null;
    },
}


function WrappedScript(record)
{
    BaseScript.call(this, record);
}

WrappedScript.prototype =
{
    __proto__: BaseScript.prototype,
    
    get attrName()  { return this._record.action1; },
    get attrValue() { if (! ("_attrValue" in this)) this._attrValue = regexp(this._record.action2); return this._attrValue; },
    
    get code()
    {
        return ScriptRunner.getWrappedScriptCode(this.attrName, this.attrValue.pattern);
    },
    
    eval: function(thumbnail)
    {
        return null;
    },
}


function lazy(object, name, self, func, args)
{
    let _name = "_" + name;
    
    object.__defineGetter__(name, function()
    {
        if (! (_name in object))
            object[_name] = func.apply(self, args);
        
        return object[_name];
    });
    
    return object;
}

function regexp(pattern)
{
    try
    {
        if (typeof(pattern) == "string")
        {
            let re = new RegExp("^(?:" + pattern + ")$");
            re.pattern = pattern;
            return re;
        }
    }
    catch (e) { /* ignore */ }
    
    return null;
}
